#include "sdk.h"
#include "M_Code_StyledTextCtrl.h"
#include "M_Code_debugger_interfaces.h"
#include "ccmanager.h"
#include "debuggermanager.h"

static const wxString s_leftBrace( _T( "([{'\"" ) );
static const wxString s_rightBrace( _T( ")]}'\"" ) );
static const int s_indicHighlight( 20 );

std::map<int, std::set<int> > M_Code_StyledTextCtrl::CharacterLexerStyles;
std::map<int, std::set<int> > M_Code_StyledTextCtrl::StringLexerStyles;
std::map<int, std::set<int> > M_Code_StyledTextCtrl::PreprocessorLexerStyles;
std::map<int, std::set<int> > M_Code_StyledTextCtrl::CommentLexerStyles;

BEGIN_EVENT_TABLE( M_Code_StyledTextCtrl, wxScintilla )
  EVT_CONTEXT_MENU( M_Code_StyledTextCtrl::OnContextMenu )
  EVT_KILL_FOCUS( M_Code_StyledTextCtrl::OnKillFocus )
  EVT_MIDDLE_DOWN( M_Code_StyledTextCtrl::OnMouseMiddleDown )
  EVT_SET_FOCUS( M_Code_StyledTextCtrl::OnSetFocus )
  EVT_KEY_DOWN( M_Code_StyledTextCtrl::OnKeyDown )
  EVT_KEY_UP( M_Code_StyledTextCtrl::OnKeyUp )
  EVT_LEFT_UP( M_Code_StyledTextCtrl::OnMouseLeftUp )
END_EVENT_TABLE()

M_Code_StyledTextCtrl::M_Code_StyledTextCtrl( wxWindow* pParent, int id, const wxPoint& pos, const wxSize& size, long style ) :
  wxScintilla( pParent, id, pos, size, style ),
  m_pParent( pParent ),
  m_lastFocusTime( 0L ),
  m_bracePosition( wxSCI_INVALID_POSITION ),
  m_lastPosition( wxSCI_INVALID_POSITION ),
  m_tabSmartJump( false ) {
  m_braceShortcutState = false;
}

M_Code_StyledTextCtrl::~M_Code_StyledTextCtrl() {
  while( GetEventHandler() != this ) {
    RemoveEventHandler( GetEventHandler() );
  }
}

void M_Code_StyledTextCtrl::operator=( const M_Code_StyledTextCtrl& ) {
  m_Throw( _T( "Can't assign an M_Code_StyledTextCtrl* !!!" ) );
}


void M_Code_StyledTextCtrl::OnKillFocus( wxFocusEvent& event ) {
  if( AutoCompActive() ) {
    AutoCompCancel();
  }
  if( CallTipActive() ) {
    CallTipCancel();
  }
  event.Skip();
}

void M_Code_StyledTextCtrl::OnSetFocus( wxFocusEvent& event ) {
  m_lastFocusTime = wxGetLocalTimeMillis();
  event.Skip();
}

void M_Code_StyledTextCtrl::OnContextMenu( wxContextMenuEvent& event ) {
  if( m_pParent ) {
    if( EditorBase* pParent = dynamic_cast<EditorBase*>( m_pParent ) ) {
      if( HaveMouseCapture() ) {
        SetMouseCapture( false );
      }
      const bool is_right_click = event.GetPosition() != wxDefaultPosition;
      const wxPoint mp( is_right_click ? event.GetPosition() : wxDefaultPosition );
      pParent->DisplayContextMenu( mp, mtEditorManager );
    } else
    { event.Skip(); }
  }
}

void M_Code_StyledTextCtrl::OnMouseMiddleDown( wxMouseEvent& event ) {
}

void M_Code_StyledTextCtrl::OnKeyDown( wxKeyEvent& event ) {
  m_lastSelectedText = GetSelectedText();
  bool emulateDwellStart = false;
  switch( event.GetKeyCode() ) {
    case _T( 'I' ): {
      if( event.GetModifiers() == wxMOD_ALT ) {
        m_braceShortcutState = true;
      }
      break;
    }
    case WXK_TAB: {
      if( m_tabSmartJump && event.GetModifiers() == wxMOD_NONE ) {
        if( !AutoCompActive() && m_bracePosition != wxSCI_INVALID_POSITION ) {
          m_lastPosition = GetCurrentPos();
          GotoPos( m_bracePosition );
          HighlightRightBrace();
          if( !m_tabSmartJump && CallTipActive() ) {
            CallTipCancel();
          }
          return;
        }
      }
    }
    break;
    case WXK_BACK: {
      if( m_tabSmartJump ) {
        if( !( event.ControlDown() || event.ShiftDown() || event.AltDown() ) ) {
          const int pos = GetCurrentPos();
          const int index = s_leftBrace.Find( ( wxChar )GetCharAt( pos - 1 ) );
          if( index != wxNOT_FOUND && ( wxChar )GetCharAt( pos ) == s_rightBrace.GetChar( index ) ) {
            CharRight();
            DeleteBack();
          }
        } else if( m_lastPosition != wxSCI_INVALID_POSITION && event.ControlDown() ) {
          GotoPos( m_lastPosition );
          m_lastPosition = wxSCI_INVALID_POSITION;
          return;
        }
      }
    }
    break;
    case WXK_RETURN:
    case WXK_NUMPAD_ENTER:
    case WXK_ESCAPE: {
      if( m_tabSmartJump ) {
        m_tabSmartJump = false;
      }
    }
    break;
    case WXK_CONTROL: {
      EmulateDwellStart();
      emulateDwellStart = true;
    }
    break;
    case WXK_UP:
    case WXK_DOWN: {
      if( event.GetModifiers() == wxMOD_NONE ) {
        if( Manager::Get()->GetCCManager()->ProcessArrow( event.GetKeyCode() ) ) {
          return;
        }
      }
    }
    break;
    default:
      break;
  }
  if( event.ControlDown() && !emulateDwellStart ) {
    EmulateDwellStart();
  }
  event.Skip();
}

void M_Code_StyledTextCtrl::OnKeyUp( wxKeyEvent& event ) {
  const int keyCode = event.GetKeyCode();
  switch( keyCode ) {
    case _T( '[' ):
    case _T( '\'' ):
    case _T( '(' ): {
      if( !AllowTabSmartJump() ) {
        break;
      }
      wxChar ch = keyCode;
      if( event.ShiftDown() ) {
        if( keyCode == _T( '\'' ) ) {
          ch = _T( '"' );
        } else if( keyCode == _T( '9' ) ) {
          ch = _T( '(' );
        } else if( keyCode == _T( '[' ) ) {
          ch = _T( '{' );
        }
      }
      int index = s_leftBrace.Find( ch );
      if( index != wxNOT_FOUND && ( wxChar )GetCharAt( GetCurrentPos() ) == s_rightBrace.GetChar( index ) ) {
        const int pos = GetCurrentPos();
        if( pos != wxSCI_INVALID_POSITION ) {
          m_tabSmartJump = true;
          m_bracePosition = pos;
        }
      } else if( keyCode == _T( '\'' ) ) {
        m_tabSmartJump = false;
      }
    }
    break;
    case _T( ']' ):
    case _T( ')' ): {
      if( !AllowTabSmartJump() ) {
        break;
      }
      if( keyCode == _T( '0' ) && !event.ShiftDown() ) {
        break;
      }
      m_tabSmartJump = false;
    }
    break;
    default:
      break;
  }
  HighlightRightBrace();
  event.Skip();
}

void M_Code_StyledTextCtrl::OnMouseLeftUp( wxMouseEvent& event ) {
  HighlightRightBrace();
  event.Skip();
}

bool M_Code_StyledTextCtrl::IsCharacter( int style ) {
  return CharacterLexerStyles[GetLexer()].find( style ) != CharacterLexerStyles[GetLexer()].end();
}

bool M_Code_StyledTextCtrl::IsString( int style ) {
  return StringLexerStyles[GetLexer()].find( style ) != StringLexerStyles[GetLexer()].end();
}

bool M_Code_StyledTextCtrl::IsPreprocessor( int style ) {
  return PreprocessorLexerStyles[GetLexer()].find( style ) != PreprocessorLexerStyles[GetLexer()].end();
}

bool M_Code_StyledTextCtrl::IsComment( int style ) {
  return CommentLexerStyles[GetLexer()].find( style ) != CommentLexerStyles[GetLexer()].end();
}

void M_Code_StyledTextCtrl::CallTipCancel() {
  if( !m_tabSmartJump ) {
    wxScintilla::CallTipCancel();
  }
}

bool M_Code_StyledTextCtrl::IsBraceShortcutActive() {
  bool state = m_braceShortcutState;
  m_braceShortcutState = false;
  return state;
}

void M_Code_StyledTextCtrl::DoBraceCompletion( const wxChar& ch ) {
  const int pos   = GetCurrentPos();
  const int style = GetStyleAt( pos );
  if( IsComment( style ) || IsComment( GetStyleAt( pos - 2 ) ) ) {
    return;
  }
  if( ch == _T( '\'' ) || ch == _T( '"' ) ) {
    if( GetCharAt( pos ) == ch ) {
      DeleteBack();
      CharRight();
    } else if( !IsString( GetStyleAt( pos - 2 ) ) && !IsCharacter( GetStyleAt( pos - 2 ) ) ) {
      InsertText( pos, ch );
    }
    return;
  }
  if( IsString( style ) || IsCharacter( style ) ) {
    return;
  }
  const wxString opBraces( _T( "([{" ) );
  const int opBraceIdx = opBraces.Find( ch );
  const wxString clBraces( _T( ")]}" ) );
  const int clBraceIdx = clBraces.Find( ch );
  if( ( opBraceIdx != wxNOT_FOUND ) || ( clBraceIdx != wxNOT_FOUND ) ) {
    if( GetCharAt( pos ) == ch ) {
      DeleteBack();
      CharRight();
    } else if( opBraceIdx != wxNOT_FOUND ) {
      int nextPos = pos;
      while( wxIsspace( GetCharAt( nextPos ) ) && ( nextPos < GetLength() ) ) {
        ++nextPos;
      }
      if( ( ( wxChar )GetCharAt( nextPos ) != clBraces[opBraceIdx] )
          || ( BraceMatch( nextPos )        != wxNOT_FOUND ) ) {
        InsertText( pos, clBraces[opBraceIdx] );
      }
    }
  }
}

bool M_Code_StyledTextCtrl::DoSelectionBraceCompletion( const wxChar& ch ) {
  if( GetLastSelectedText().IsEmpty() ) {
    return false;
  }
  const wxString braces( _T( "([{<'\")]}>'\"" ) );
  const int braceAIdx = braces.Find( ch, true );
  if( braceAIdx == wxNOT_FOUND ) {
    return false;
  }
  const int braceBIdx = ( braceAIdx + ( braces.Length() / 2 ) ) % braces.Length();
  BeginUndoAction();
  DeleteBack();
  if( braceAIdx < braceBIdx ) {
    InsertText( GetCurrentPos(), braces[braceAIdx] + GetLastSelectedText() + braces[braceBIdx] );
  } else {
    AddText( braces[braceBIdx] + GetLastSelectedText() + braces[braceAIdx] );
  }
  EndUndoAction();
  return true;
}

bool M_Code_StyledTextCtrl::AllowTabSmartJump() {
  const int pos = GetCurrentPos();
  if( pos == wxSCI_INVALID_POSITION ) {
    return false;
  }
  const int style = GetStyleAt( pos );
  if( IsString( style ) || IsCharacter( style ) || IsComment( style ) || IsPreprocessor( style ) ) {
    return !m_tabSmartJump;
  }
  return true;
}

void M_Code_StyledTextCtrl::HighlightRightBrace() {
  if( m_bracePosition == wxSCI_INVALID_POSITION ) {
    return;
  }
  int pos = GetCurrentPos();
  if( pos == wxSCI_INVALID_POSITION ) {
    return;
  }
  const static wxColour caretForeground = GetCaretForeground();
  const static int caretWidth = GetCaretWidth();
  const int curLine = GetCurrentLine();
  const int len = GetLength();
  if( m_tabSmartJump && ( curLine == LineFromPosition( m_bracePosition ) ) ) {
    SetIndicatorCurrent( s_indicHighlight );
    const int indPos = GetLineIndentPosition( curLine );
    IndicatorClearRange( indPos, GetLineEndPosition( curLine ) - indPos );
    do {
      if( pos >= len ) {
        break;
      }
      wxString cur( ( wxChar )GetCharAt( pos ) );
      if( cur == _T( "\n" ) ) {
        break;
      }
      int style = GetStyleAt( pos );
      if( IsComment( style ) ) {
        continue;
      }
      if( IsString( style ) || IsCharacter( style ) ) {
        const int nextOne = ( pos == len ) ? GetStyleAt( pos ) : GetStyleAt( pos + 1 );
        if( IsCharacter( nextOne ) || IsString( nextOne ) ) {
          continue;
        }
      }
      if( s_rightBrace.Contains( cur ) ) {
        SetCaretForeground( wxColour( 255, 0, 0 ) );
        SetCaretWidth( caretWidth + 1 );
        IndicatorSetForeground( s_indicHighlight, wxColour( 80, 236, 120 ) );
        IndicatorSetStyle( s_indicHighlight, wxSCI_INDIC_ROUNDBOX );
        #ifndef wxHAVE_RAW_BITMAP
        IndicatorSetUnder( s_indicHighlight, true );
        #endif
        SetIndicatorCurrent( s_indicHighlight );
        IndicatorFillRange( pos, 1 );
        m_bracePosition = pos + 1;
        return;
      }
    } while( ++pos );
  }
  m_bracePosition = wxSCI_INVALID_POSITION;
  m_lastPosition = wxSCI_INVALID_POSITION;
  m_tabSmartJump = false;
  SetIndicatorCurrent( s_indicHighlight );
  IndicatorClearRange( 0, len );
  SetCaretForeground( caretForeground );
  SetCaretWidth( caretWidth );
}

void M_Code_StyledTextCtrl::EmulateDwellStart() {
  EditorBase *editor = static_cast<EditorBase*>( m_pParent );
  CodeBlocksEvent event( cbEVT_EDITOR_TOOLTIP );
  wxPoint pt( ScreenToClient( wxGetMousePosition() ) );
  event.SetX( pt.x );
  event.SetY( pt.y );
  int pos = PositionFromPoint( pt );
  int style = GetStyleAt( pos );
  event.SetInt( style );
  event.SetEditor( editor );
  event.SetExtraLong( 0 );
  Manager::Get()->GetPluginManager()->NotifyPlugins( event );
}

void M_Code_StyledTextCtrl::EnableTabSmartJump( bool enable ) {
  m_tabSmartJump = enable;
  m_bracePosition = GetCurrentPos();
  HighlightRightBrace();
}

std::map<int, std::set<int> > &M_Code_StyledTextCtrl::GetCharacterLexerStyles() {
  return CharacterLexerStyles;
}

std::map<int, std::set<int> > &M_Code_StyledTextCtrl::GetStringLexerStyles() {
  return StringLexerStyles;
}

std::map<int, std::set<int> > &M_Code_StyledTextCtrl::GetPreprocessorLexerStyles() {
  return PreprocessorLexerStyles;
}

std::map<int, std::set<int> > &M_Code_StyledTextCtrl::GetCommentLexerStyles() {
  return CommentLexerStyles;
}

void M_Code_StyledTextCtrl::MakeNearbyLinesVisible( int line ) {
  const int dist = VisibleFromDocLine( line ) - GetFirstVisibleLine();
  if( dist >= 0 && dist < 2 ) {
    LineScroll( 0, dist - 2 );
  } else if( dist >= LinesOnScreen() - 2 ) {
    LineScroll( 0, 3 + dist - LinesOnScreen() );
  }
}
